package net.dwade.dubbo.adapter;

import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.spring.ServiceBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于支持dubbo服务适配器，如果interface属性以StringAdapter结尾，并且ref属性以$StringAdapter结尾，
 * 则根据原有的接口动态生成String入参出参的新接口，并且使用动态代理调用原有
 * <p>
 * eg:&lt;dubbo:service registry="esb-registry" interface="com.sitech.miso.PaymentServiceStringAdapter" 
 * ref="paymentService$StringAdapter" /&gt;
 * </p>
 * @author huangxf
 * @date 2017年7月24日
 */
public class DubboProviderAdapterSupport implements BeanFactoryPostProcessor, InitializingBean {

	private Logger logger = LoggerFactory.getLogger( this.getClass() );
	
	/**
	 * 默认使用jdk的动态代理
	 */
	private ServiceAdapterProxyFactory proxyFactory;
	
	/**
	 * 用于保存provider创建的InvocationHandler，使用代理的方式使其支持spring bean的部分生命周期
	 * @see #afterPropertiesSet()
	 */
	private Set<Object> providerProxySet = Collections.newSetFromMap( new ConcurrentHashMap<Object, Boolean>() );
	
	public void setProxyFactory(ServiceAdapterProxyFactory proxyFactory) {
		this.proxyFactory = proxyFactory;
        Assert.notNull( proxyFactory, "ProxyFactory is null." );
	}

	/**
	 * 遍历所有的Bean定义，找到ServiceBean的定义，如果对应的interface以StringAdapter结尾,并且ref属性以$StringAdapter结尾，则创建适配器
	 * @see ServiceAdapterProxyFactory
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		String[] names = beanFactory.getBeanDefinitionNames();
		for ( String name : names ) {
			BeanDefinition definition = beanFactory.getBeanDefinition( name );
			String beanClassName = definition.getBeanClassName();
			if ( ServiceBean.class.getName().equals( beanClassName ) ) {
				if ( !isCandidateInterface( definition ) ) {
					continue;
				}
				String interfaceName = (String)definition.getPropertyValues().get( "interface" );
				try {
					Object adapter = this.createAndRegisterAdapter( definition, beanFactory );
					logger.info( "create adapter instance:{}, interface:{}", adapter, interfaceName );
				} catch ( Throwable e ) {
					logger.error( "error create adapter for interface:{}", interfaceName );
					throw new BeanCreationException( "DubboProviderAdapterSupport create adapter error", e );
				}
			}
		}
	}
	
	/**
	 * 创建XXXServiceStringAdapter的动态代理类，并且注册到spring容器中
	 * @param serviceBeanDef
	 * @param beanFactory
	 * @return 返回被代理的对象
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	protected Object createAndRegisterAdapter( BeanDefinition serviceBeanDef, ConfigurableListableBeanFactory beanFactory ) 
			throws Throwable {
		
		// 根据配置的接口，生成String入参出参的接口
		String stringAdapterInterfaceName = (String)serviceBeanDef.getPropertyValues().get( "interface" );
		String sourceInterfaceName = StringUtils.removeEnd( stringAdapterInterfaceName, "StringAdapter" );
		Class<?> stringAdapterClass = StringAdapterInterfaceUtils.
				createAndWriteByteCode( sourceInterfaceName, stringAdapterInterfaceName );
		
		// 根据原有的接口名称，获取对应的实现类，但是由于是在postProcessBeanFactory方法中处理，只能引用对应的bean
		BeanReference ref = (BeanReference)serviceBeanDef.getPropertyValues().get( "ref" );
		String sourceRefBeanName = StringUtils.removeEnd( ref.getBeanName(), "$StringAdapter" );
		
		// 生成jdk动态代理，并注册到spring容器中，注意：在创建proxy的时候传入的是引用的beanName
		Class<?> sourceInterface = ClassUtils.forName( sourceInterfaceName, null );
		Object proxy = proxyFactory.createProviderProxy( sourceRefBeanName, sourceInterface, stringAdapterClass );
		beanFactory.registerSingleton( ref.getBeanName(), proxy );
		
		return proxy;
		
	}

	/**
	 * 判断是否是满足条件的接口
	 * @param serviceBeanDef
	 * @return
	 */
	protected boolean isCandidateInterface( BeanDefinition serviceBeanDef ) {
		String interfaceName = (String)serviceBeanDef.getPropertyValues().get( "interface" );
		BeanReference ref = (BeanReference)serviceBeanDef.getPropertyValues().get( "ref" );
		if ( interfaceName.endsWith( "StringAdapter" ) 
				&& ref.getBeanName().endsWith( "$StringAdapter" ) ) {
			return true;
		}
		return false;
	}
	
	/**
	 * 对创建的proxy调用afterPropertiesSet方法，方便进行初始化的处理操作
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		for ( Object handler : providerProxySet ) {
			if ( handler instanceof InitializingBean ) {
				((InitializingBean) handler).afterPropertiesSet();
			}
		}
	}

}